package com.retry.task.admin.infrastructure.scheduler;

import com.google.common.collect.Maps;
import com.retry.task.admin.constants.RetryTaskStatusEnum;
import com.retry.task.admin.constants.SystemLocks;
import com.retry.task.admin.dal.model.query.RetryTaskLogQuery;
import com.retry.task.admin.dal.model.query.RetryTaskQuery;
import com.retry.task.admin.utils.DateUtils;
import com.retry.task.core.model.RetryTaskDTO;
import com.retry.task.core.model.RetryTaskLogDTO;
import com.retry.task.core.model.base.PageResult;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author gao.gwq
 * @version 1.0
 * @date 2022/5/11  14:15
 * @Description TODO 异常作业校验 执行失败的或者执行超长的
 */
@Component
public class FailTaskCheckScheduler extends BaseScheduler {
    private static final Logger LOGGER = LoggerFactory.getLogger(FailTaskCheckScheduler.class);

    private Thread thread;

    @Override
    public void afterPropertiesSet() throws Exception {
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                LOGGER.info(">>>>>>>>>>>>>>retry-task fail task check start<<<<<<<<<<<<<<");
                while (true) {
                    boolean lockFlag = false;
                    try {
                        TimeUnit.SECONDS.sleep(600);
                        lockFlag = mysqlLock.tryLock(SystemLocks.FAIL_TASK_CHECK_LOCK, isTest);
                        if (lockFlag == false) {
                            continue;
                        }
                        process();
                    } catch (Exception ex) {
                        LOGGER.error("retry-task fail task check error {}", ex.getMessage(), ex);
                    } finally {
                        if (lockFlag == true) {
                            mysqlLock.releasLock(SystemLocks.FAIL_TASK_CHECK_LOCK, isTest);
                        }
                    }
                }

            }
        });
        thread.setDaemon(true);
        thread.setName("check fail task thread");
        thread.start();
    }

    public void process() {
        Map<String, String> paramsMap = Maps.newHashMap();

        RetryTaskQuery query = buildRetryTaskQuery(paramsMap);
        processTask(query, retryTaskDTOS -> {
            doProcessRetryTask(retryTaskDTOS);
        });
    }

    private void doProcessRetryTask(List<RetryTaskDTO> retryTaskDTOS) {
        if (CollectionUtils.isEmpty(retryTaskDTOS)) {
            return;
        }
        for (RetryTaskDTO retryTaskDTO : retryTaskDTOS) {
            Long logId = retryTaskDTO.getCurrentLogId();
            RetryTaskLogQuery logQuery = new RetryTaskLogQuery();
            logQuery.setId(logId);
            PageResult<List<RetryTaskLogDTO>> logResult = retryTaskLogService.queryRetryTaskLog(logQuery);
            if (!logResult.isSuccess()) {
                LOGGER.warn("CheckRetryTaskProcessor-doProcessRetryTask,can not find retry log by id {},error {}",
                    logId,
                    logResult.getErrMsg());
                continue;
            }
            List<RetryTaskLogDTO> retryTaskLogDTOS = logResult.getData();
            if (CollectionUtils.isEmpty(retryTaskLogDTOS)) {
                LOGGER.warn("CheckRetryTaskProcessor-doProcessRetryTask,can not find retry log by id {}", logId);
                continue;
            }
            RetryTaskLogDTO logDTO = retryTaskLogDTOS.get(0);
            switchRetryTask(retryTaskDTO, logDTO);
        }
    }

    private RetryTaskQuery buildRetryTaskQuery(Map<String, String> paramsMap) {
        RetryTaskQuery query = new RetryTaskQuery();
        int pageSize = 300;
        String idListStr = paramsMap.get("idList");
        if (StringUtils.isNotEmpty(idListStr)) {
            String[] idListArr = idListStr.split(",");
            List<Long> idList = Arrays.stream(idListArr).map(id -> Long.valueOf(id)).collect(Collectors.toList());
            query.setIdList(idList);
        }
        String pageSizeStr = paramsMap.get("pageSize");
        if (StringUtils.isNotEmpty(pageSizeStr)) {
            Integer pageSizeInteger = Integer.valueOf(pageSizeStr);
            if (pageSizeInteger > 300) {
                pageSizeInteger = 300;
            }
            pageSize = pageSizeInteger;
        }
        query.setPageNum(pageSize);
        query.setStatus(RetryTaskStatusEnum.PROGRESS.getCode());
        query.setIsTest(isTest);
        return query;
    }

    private void switchRetryTask(RetryTaskDTO retryTaskDTO, RetryTaskLogDTO retryTaskLogDTO) {
        IProcessRetryLog iProcessRetryLog = null;
        if (retryTaskDTO.getStatus() == RetryTaskStatusEnum.TO_START.getCode()) {
            iProcessRetryLog = new ToStart();
        }
        if (retryTaskDTO.getStatus() == RetryTaskStatusEnum.PROGRESS.getCode()) {
            iProcessRetryLog = new Process();
        }
        if (iProcessRetryLog == null) {
            return;
        }
        iProcessRetryLog.process(retryTaskDTO, retryTaskLogDTO);
    }

    //待开始，消息已经发送，但是没有收到
    // 1:收到消息，但是回调失败
    // 2：未收到消息
    // 如果一个小时收不到消息，则更改任务状态为待开始
    public class ToStart implements IProcessRetryLog {
        @Override
        public void process(RetryTaskDTO taskDTO, RetryTaskLogDTO logDTO) {
            Date current = new Date();
            Date logCreateDate = logDTO.getGmtModified();
            Date addDate = DateUtils.addSecond(logCreateDate, 20 * 60);
            if (current.before(addDate)) {
                return;
            }
            int retryNum = taskDTO.getRetryNum();
            if (retryNum > 1) {
                taskDTO.setRetryNum(taskDTO.getRetryNum() - 1);
                taskDTO.setStatus(RetryTaskStatusEnum.TO_START.getCode());
            }
            if (retryNum == 1) {
                taskDTO.setRetryNum(taskDTO.getRetryNum() - 1);
                taskDTO.setStatus(RetryTaskStatusEnum.TIME_OUT_NO_MESSAGE.getCode());
            }
            logDTO.setStatus(RetryTaskStatusEnum.TIME_OUT_NO_MESSAGE.getCode());
            centerTransactionTemplate.execute(new TransactionCallback<Integer>() {
                @Override
                public Integer doInTransaction(TransactionStatus status) {
                    retryTaskLogService.updateRetryTaskLogById(logDTO);
                    retryTaskService.updateRetryTaskById(taskDTO);
                    return 1;
                }
            });

        }
    }

    //收到消息，切已经变更日志状态，任务进行中
    //1：任务未执行结束
    //2:执行结束，回调失败
    public class Process implements IProcessRetryLog {
        @Override
        public void process(RetryTaskDTO taskDTO, RetryTaskLogDTO logDTO) {
            Date current = new Date();
            Date logCreateDate = logDTO.getGmtModified();
            Date addDate = DateUtils.addSecond(logCreateDate, 20 * 60);
            if (current.before(addDate)) {
                return;
            }
            int retryNum = taskDTO.getRetryNum();
            if (retryNum > 1) {
                taskDTO.setRetryNum(taskDTO.getRetryNum() - 1);
                taskDTO.setStatus(RetryTaskStatusEnum.TO_START.getCode());
            }
            if (retryNum == 1) {
                taskDTO.setRetryNum(taskDTO.getRetryNum() - 1);
                taskDTO.setStatus(RetryTaskStatusEnum.TIME_OUT_NO_CALLBACK.getCode());
                taskDTO.setStatus(RetryTaskStatusEnum.TIME_OUT_NO_UPDATE.getCode());
            }
            logDTO.setStatus(RetryTaskStatusEnum.TIME_OUT_NO_UPDATE.getCode());
            centerTransactionTemplate.execute(new TransactionCallback<Integer>() {
                @Override
                public Integer doInTransaction(TransactionStatus status) {
                    retryTaskLogService.updateRetryTaskLogById(logDTO);
                    retryTaskService.updateRetryTaskById(taskDTO);
                    return 1;
                }
            });
        }
    }

}

interface IProcessRetryLog {

    void process(RetryTaskDTO taskDTO, RetryTaskLogDTO logDTO);
}